package com.metersbonwe.seq;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.springframework.util.Assert;

import com.metersbonwe.seq.po.SequenceInfo;

public class SequenceManager {

    Logger                                             logger     = Logger.getLogger(SequenceManager.class);

    SequenceProvider                                   sequenceProvider;

    private final static Object                        safe       = new Object();

    /**
     * 内存池大小
     */
    private volatile int                               bufferSize = 20000;

    /**
     * 快消费预取倍率
     */
    private volatile int                               multiple   = 10;

    /**
     * 取内存队列等待超时
     */
    private volatile int                               timeout    = 100;

    /**
     * 循环间隔 毫秒
     */
    private volatile int                               interval   = 50;

    private static volatile Map<String, SequenceQueue> queueCache = new HashMap<String, SequenceQueue>();

    public Long nextVal(String name) {

        SequenceQueue queue = getQueue(name);
        try {
            Long l = queue.poll(timeout, TimeUnit.MILLISECONDS);
            if (l != null) {
                return l;
            }
        } catch (Exception e) {
            logger.warn(e.getMessage(), e);
        }
        logger.info(" queue " + name + " is empty");
        return nextSeqForDb(name);
    }

    private Long nextSeqForDb(String name) {
        return nextSeqForDb(name, -1);
    }

    private Long nextSeqForDb(String name, int size) {
        return sequenceProvider.nextSeq(name, size);
    }

    private SequenceQueue getQueue(String name) {
        if (queueCache.containsKey(name)) {
            return queueCache.get(name);
        }
        synchronized (safe) {
            if (queueCache.containsKey(name)) {
                return queueCache.get(name);
            }
            queueCache.put(name, createQueue(name, bufferSize));
        }
        return queueCache.get(name);
    }

    private SequenceQueue createQueue(String name, int size) {
        SequenceInfo seq = sequenceProvider.getConfig(name);
        Assert.notNull(seq, " SequenceQueue " + name + " not fount data .");
        SequenceQueue queue = new SequenceQueue(seq.getCode(), size, seq.getIncrement());
        startFillQueueThread(queue);
        return queue;
    }

    private void startFillQueueThread(SequenceQueue queue) {
        FillQueueRunnable fqr = new FillQueueRunnable(queue);
        new Thread(fqr).start();
    }

    public class FillQueueRunnable implements Runnable {
        SequenceQueue queue;

        public FillQueueRunnable(SequenceQueue queue) {
            super();
            this.queue = queue;
        }

        public void run() {
            logger.info("start queue " + queue.getQueueName() + " fill thread");

            while (true) {
                int size = queue.size();
                int cap = queue.getQueueCapacity();

                int count = 0;

                if (size < 1 || cap / size > 2) {
                    count = cap - size;
                } else {
                    continue;
                }

                if ((cap - count) < cap / 4) {
                    count = multiple * cap;
                }

                Long s = nextSeqForDb(queue.getQueueName(), count);

                try {
                    queue.add(s, count);

                    Thread.sleep(interval);
                } catch (InterruptedException e) {
                    logger.warn(e.getMessage(), e);
                }
            }

        }
    }

    public int getBufferSize() {
        return bufferSize;
    }

    public void setBufferSize(int bufferSize) {
        this.bufferSize = bufferSize;
    }

    public static Map<String, SequenceQueue> getQueueCache() {
        return queueCache;
    }


    public SequenceProvider getSequenceProvider() {
        return sequenceProvider;
    }

    public void setSequenceProvider(SequenceProvider sequenceProvider) {
        this.sequenceProvider = sequenceProvider;
    }

    public int getMultiple() {
        return multiple;
    }

    public void setMultiple(int multiple) {
        this.multiple = multiple;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public int getInterval() {
        return interval;
    }

    public void setInterval(int interval) {
        this.interval = interval;
    }

}
